/*
 * Context.java
 *
 * Created on November 17, 2007, 4:38 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.util;

import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;
import hextd.HexTD;
import hextd.blackBox.BlackBox;
import hextd.hex.Hex;
import hextd.sprite.EnemySprite;
import hextd.tower.Tower;
import hextd.wave.Path;
import hextd.wave.Wave;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Hashtable;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.text.html.StyleSheet;

/**
 *
 * @author hylke
 */
public class Context {

	public String levelTitle = "";
	public String levelInfo = "";
	public String backgroundImage = "images/sunset.jpg";
	public Object PaintingLock = new Object();
	public Object gameTimeLock = new Object();
	public int gameTime;
	private HexTD mainApp;
	public String VERSION;
	public BlackBox blackBoxCurrent;
	public URL contextUrl;
	public URL mapContextUrl;
	public colorSelection colors;
	public Hex[][] grid;
	public int gridWidth;
	public int gridHeight;
	public float offsetX;
	public float offsetY;
	public float minX,  minY,  maxX,  maxY;
	public float paintScale;
	public float strokeScale;
	public float hexPaintWidth;
	public float hexFloatWidth;
	public HexTD.gameState gameState = HexTD.gameState.selecing;
	public String mapLocation;
	public String mapMD5;
	public EnemySprite[] enemies;
	public Vector<Tower> towers;
	private int enemyCount = 0;
	private Vector<Path> paths;
	private int wave = 0;
	private Vector<Wave> waves;
	private boolean waveActive = false;
	private int lives = 20;
	public int livesStart = 20;
	private long credits = 0;
	public long creditsStart = 0;
	public int interestStart = 3;
	public int interestIncrease = 3;
	private int bonus = 0;
	private int bonusWait = 0;
	private long score = 0;
	private Vector<ContextListener> contextListeners;
	private Vector<TowerListener> towerListeners;
	private Vector<WaveStartListener> waveListeners;
	private SVGUniverse svgUniverse;
	private Hashtable<String, URI> svgImages;
	private Hashtable<String, StyleSheet> styleSheets;
	private Hashtable<String, BufferedImage> images;
	private Cache cache;

	/** Creates a new instance of Context */
	public Context() {
		this.contextListeners = new Vector<ContextListener>();
		this.towerListeners = new Vector<TowerListener>();
		this.waveListeners = new Vector<WaveStartListener>();
		this.styleSheets = new Hashtable<String, StyleSheet>();
		this.svgImages = new Hashtable<String, URI>();
		this.towers = new Vector<Tower>();
		this.paths = new Vector<Path>();
		this.waves = new Vector<Wave>();
		this.cache = new Cache();
	}

	public Context(HexTD mainApp) {
		this.mainApp = mainApp;
		this.contextListeners = new Vector<ContextListener>();
		this.towerListeners = new Vector<TowerListener>();
		this.waveListeners = new Vector<WaveStartListener>();
		this.styleSheets = new Hashtable<String, StyleSheet>();
		this.svgImages = new Hashtable<String, URI>();
		this.images = new Hashtable<String, BufferedImage>();
		this.towers = new Vector<Tower>();
		this.paths = new Vector<Path>();
		this.waves = new Vector<Wave>();
		this.cache = new Cache();
	}

	public boolean loadMap(String maplocation) {
		this.resetMap();
		boolean retval = MapIO.readLevel(this, maplocation);
		this.mainApp.mapChanged();
		return retval;
	}

	public void loadMap(URL fetchUrl, String relativeLoc) {
		this.resetMap();
		MapIO.readLevel(this, fetchUrl, relativeLoc);
		this.mainApp.mapChanged();
	}

	public void loadMap(BufferedReader b, String maplocation) {
		this.resetMap();
		MapIO.readLevel(this, b, maplocation);
		this.mainApp.mapChanged();
	}

	public void resetMap() {
		this.removeAllEnemies();
		this.clearTowers();
		this.setCredits(this.creditsStart);
		this.setInterest(this.interestStart, this.interestIncrease);
		this.resetLives(this.livesStart);
		this.resetBonus();
		this.resetScore();
		this.resetWaves();
		this.gameTime = 0;
		this.mainApp.resetMap();
	}

	public void highlightHex(int[] hexC) {
		this.mainApp.highLightHex(hexC);
	}

	public Hex getHex(int col, int row) {
		return this.grid[col][row];
	}

	public void setEnemyCount(int c) {
		this.enemyCount = c;
	}

	public int getEnemyCount() {
		return this.enemyCount;
	}

	public void removeEnemy() {
		this.enemyCount--;
		this.mainApp.enemyDied(this.enemyCount);
		if (this.enemyCount == 0) {
			this.payInterest();
			if (this.wave >= this.waves.size()) {
				this.blackBoxCurrent.endGame();
			}
		}
	}

	public void removeAllEnemies() {
		if (this.enemyCount > 0) {
			this.enemyCount = 0;
			this.enemies = new EnemySprite[0];
			this.mainApp.enemyDied(this.enemyCount);
		}
	}

	public EnemySprite findClosestCreep(float paintX, float paintY, float maxRange) {
		EnemySprite esFound = null;
		float maxR2 = maxRange * maxRange;
		float closestR2 = Float.MAX_VALUE;
		float r2, dx, dy;
		if (this.enemies != null) {
			for (EnemySprite es : this.enemies) {
				if (!es.isDead()) {
					dx = es.getPaintX() - paintX;
					dy = es.getPaintY() - paintY;
					r2 = dx * dx + dy * dy;
					if (r2 < closestR2) {
						esFound = es;
						closestR2 = r2;
					}
				}
			}
			if (closestR2 < maxR2) {
				return esFound;
			} else {
				return null;
			}
		} else {
			return null;
		}
	}

	// Wave handling methods
	public void clearWaves() {
		this.wave = 0;
		this.waves.clear();
	}

	public void resetWaves() {
		this.wave = 0;
	}

	public Vector<Wave> getWaves() {
		return this.waves;
	}

	public int getWave() {
		return this.wave;
	}

	public int getWaveCount() {
		return this.waves.size();
	}

	public void nextWave() {
		System.out.println("Sending next wave: " + this.wave);
		Wave w = this.waves.get(this.wave);
		this.enemies = w.getEnemies();
		for (int i = 0; i < this.enemies.length; i++) {
			this.enemies[i].resetScale();
		}
		this.wave++;
		this.waveActive = true;
		this.setEnemyCount(w.enemyCount());
		this.fireWaveStartedEvent();
	}

	public void addWaveStartListener(WaveStartListener l) {
		this.waveListeners.add(l);
	}

	public void removeWaveStartListener(WaveStartListener l) {
		this.waveListeners.remove(l);
	}

	private void fireWaveStartedEvent() {
		for (int i = 0; i < this.waveListeners.size(); i++) {
			this.waveListeners.get(i).waveStarted();
		}
	}

	private void payInterest() {
		long interest = (long) (this.credits * (this.interestStart + this.bonus * this.interestIncrease) / 100l);
		this.bonus += this.bonusWait;
		this.bonusWait = 0;
		this.credits += interest;
		this.score += interest;
		this.fireMoneyChangedEvent();
	}

	public void setInfoText(String s) {
		this.mainApp.setInfoText(s);
	}

	public void addScore(int s) {
		if (s < 0) {
			System.out.println("Context::addScore: Adding negative score? " + s);
		}
		this.score += s;
	}

	public void deductScore(int s) {
		if (s < 0) {
			System.out.println("Context::deductScore: Deducting negative score? " + s);
		}
		this.score -= s;
	}

	public long getScore() {
		return this.score;
	}

	public void resetScore() {
		this.score = 0;
	}

	public long getCredits() {
		return this.credits;
	}

	public void setCredits(long credits) {
		this.credits = credits;
		this.fireMoneyChangedEvent();
	}

	public void setInterest(int start, int increase) {
		this.interestStart = start;
		this.interestIncrease = increase;
		this.fireInterestChangedEvent();
	}

	public boolean canPay(int amount) {
		//System.out.println("Context::canPay: checking "+amount);
		if (amount > 0) {
			return (this.credits >= amount);
		} else {
			return (this.bonus + this.bonusWait > 0);
		}
	}

	public boolean doPay(int amount) {
		//System.out.println("Context::doPay: checking "+amount);
		if (amount > 0) {
			if (this.canPay(amount)) {
				this.credits -= amount;
				//System.out.println("Context::doPay: credits: "+this.credits);
				this.fireMoneyChangedEvent();
				return true;
			} else {
				return false;
			}
		} else {
			if (this.canPay(amount)) {
				if (this.bonusWait > 0) {
					this.bonusWait--;
				} else {
					this.bonus--;
				}
				this.fireMoneyChangedEvent();
				return true;
			} else {
				return false;
			}
		}
	}

	public void doReceive(int amount) {
		if (amount > 0) {
			this.credits += amount;
			this.fireMoneyChangedEvent();
		} else {
			this.bonusWait++;
			this.fireMoneyChangedEvent();
		}

	}

	public void addTower(Tower t) {
		this.towers.add(t);
		this.fireTowerAddedEvent(t);
	}

	public void removeTower(Tower t) {
		this.towers.remove(t);
		this.fireTowerRemovedEvent(t);
	}

	public void sellTower(Tower t) {
		int[] hexC = t.getHex().getLocation();
		this.blackBoxCurrent.sellTower(hexC[0], hexC[1]);

	}

	public void clearTowers() {
		this.mainApp.unSelectTower();
		Tower t;
		for (int i = this.towers.size() - 1; i >= 0; i--) {
			t = this.towers.get(i);
			Hex hex = t.getHex();
			hex.unSetTower();
			t.doCleanup();
			this.towers.remove(t);
			this.fireTowerRemovedEvent(t);
		}
	}

	public void nextMode(Tower t, int m) {
		int[] hexC = t.getHex().getLocation();
		this.blackBoxCurrent.setMode(hexC[0], hexC[1], m, t.getMode(m) + 1);
	}

	public void setMode(Tower t, int m, int v) {
		int[] hexC = t.getHex().getLocation();
		this.blackBoxCurrent.setMode(hexC[0], hexC[1], m, v);
	}

	public void addTowerListener(TowerListener l) {
		this.towerListeners.add(l);
	}

	public void removeTowerListener(TowerListener l) {
		this.towerListeners.remove(l);
	}

	private void fireTowerAddedEvent(Tower t) {
		for (int i = 0; i < this.towerListeners.size(); i++) {
			this.towerListeners.get(i).towerBuild(t);
		}
	}

	private void fireTowerRemovedEvent(Tower t) {
		for (int i = 0; i < this.towerListeners.size(); i++) {
			this.towerListeners.get(i).towerRemoved(t);
		}
	}

	public void addContextListener(ContextListener l) {
		this.contextListeners.add(l);
	}

	public void removeContextListener(ContextListener l) {
		this.contextListeners.remove(l);
	}

	private void fireMoneyChangedEvent() {
		for (int i = 0; i < this.contextListeners.size(); i++) {
			this.contextListeners.get(i).moneyChanged();
		}
	}

	private void fireLivesChangedEvent() {
		for (int i = 0; i < this.contextListeners.size(); i++) {
			this.contextListeners.get(i).livesChanged();
		}
	}

	private void fireInterestChangedEvent() {
		for (int i = 0; i < this.contextListeners.size(); i++) {
			this.contextListeners.get(i).interestChanged();
		}
	}

	public Vector<Path> getPaths() {
		return this.paths;
	}

	public Path getPath(int p) {
		if (p < 0) {
			p = 0;
		}
		if (p > this.paths.size() - 1) {
			p = this.paths.size() - 1;
		}
		return this.paths.get(p);
	}

	public int getLives() {
		return lives;
	}

	public void removeLife() {
		this.lives--;
		if (this.lives <= 0 && this.blackBoxCurrent != null) {
			this.blackBoxCurrent.endGame();
		}
		this.fireLivesChangedEvent();
	}

	public void resetLives(int n) {
		this.lives = n;
		this.fireLivesChangedEvent();
	}

	public void addBonus() {
		this.bonus++;
		this.fireInterestChangedEvent();
	}

	public void useBonus() {
		this.bonus--;
		this.fireInterestChangedEvent();
	}

	public int getInterest() {
		return interestStart + this.bonus * this.interestIncrease;
	}

	public int getBonus() {
		return bonus + bonusWait;
	}

	public void resetBonus() {
		this.bonus = 0;
		this.bonusWait = 0;
		this.fireInterestChangedEvent();
	}

	public void showAlert(String alert) {
		this.mainApp.showAlert(alert);
	}

	public Cache getCache() {
		return this.cache;
	}

	public StyleSheet loadStyleSheet(String name) {
		if (this.styleSheets.containsKey(name)) {
			return this.styleSheets.get(name);
		} else {
			StyleSheet s = new StyleSheet();
			URL u;
			try {
				u = new URL(this.contextUrl, name);
				s.importStyleSheet(u);
			} catch (MalformedURLException ex) {
				ex.printStackTrace();
			}
			this.styleSheets.put(name, s);
			return s;
		}
	}

	public void clearImages() {
		this.images.clear();
	}

	public BufferedImage loadImage(String location) {
		if (this.images.containsKey(location)) {
			return this.images.get(location);
		} else {
			BufferedImage i = null;
			URL u;
			try {
				u = new URL(this.contextUrl, location);
				i = ImageIO.read(u);
			} catch (MalformedURLException ex) {
				System.out.println("Context::loadImage: MalformedURLException reading location: " + location);
			} catch (IOException ex) {
				System.out.println("Context::loadImage: IOException reading location: " + location);
			}
			if (i == null) {
				i = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
			}
			this.images.put(location, i);
			return i;
		}
	}

	public URI loadSvg(String name, String location) {
		if (this.svgUniverse == null) {
			this.svgUniverse = SVGCache.getSVGUniverse();
		}
		if (!this.svgImages.containsKey(name)) {
			URL url;
			try {
				url = new URL(this.contextUrl, location);
			} catch (MalformedURLException ex) {
				ex.printStackTrace();
				return null;
			}
			URI uri = this.svgUniverse.loadSVG(url);
			this.svgImages.put(name, uri);
		}
		return this.svgImages.get(name);
	}

	public void renderSvgImage(String name, Graphics2D g, int w, int h) {
		AffineTransform oldXform = g.getTransform();
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		URI image = this.svgImages.get(name);
		SVGDiagram diagram = svgUniverse.getDiagram(image);
		Rectangle2D.Double rect = new Rectangle2D.Double();
		diagram.getViewRect(rect);

		AffineTransform scaleXform = new AffineTransform();
		//scaleXform.setToTranslation(50,50);
		scaleXform.setToScale(w / rect.width, h / rect.height);
		g.transform(scaleXform);

		//diagram.setDeviceViewport(new Rectangle(0, 0, w, h));
		diagram.setIgnoringClipHeuristic(true);
		try {
			diagram.render(g);
		} catch (SVGException ex) {
			ex.printStackTrace();
		}
		g.setTransform(oldXform);
	}

	public boolean isWaveActive() {
		return waveActive;
	}

	public void setWaveActive(boolean waveActive) {
		this.waveActive = waveActive;
	}
}
